import CryptoJS from 'crypto-js';

type TTSParams = {
    vcn?: string;
    speed?: number;
    volume?: number;
    pitch?: number;
    sampleRate?: 16000 | 8000;
};

export class XfTTSService {
    // 使用图片中提供的正确认证信息
    private readonly APPID = '4aa4d386';
    private readonly APISecret = 'YTMzNDcxNWRhODY4ZTY0ODczNDRmYmE2';
    private readonly APIKey = '3a1d98049a35594c5b7f8065d8d392e1';
    private readonly host = 'tts-api.xfyun.cn';
    private readonly path = '/v2/tts';
    private ws: WebSocket | null = null;
    private audioContext: AudioContext | null = null;

    async speak(text: string, params: TTSParams = {}): Promise<void> {
        if (!text?.trim()) return;
        await this.ensureAudio();
        const url = `wss://${this.host}${this.path}?${this.authQuery()}`;
        await this.open(url, text, params);
    }

    stop() {
        // 立即关闭WebSocket连接，停止语音合成
        try {
            this.ws?.close();
        } catch { }
        this.ws = null;

        // 彻底停止当前音频播放
        if (this.audioContext && this.audioContext.state !== 'closed') {
            try {
                // 暂停音频上下文，彻底停止播放
                this.audioContext.suspend();
                // 断开所有音频节点连接
                this.audioContext.close();
                this.audioContext = null;
            } catch { }
        }
    }

    private async ensureAudio() {
        if (!this.audioContext) {
            this.audioContext = new AudioContext({ sampleRate: 16000 });
        }
        if (this.audioContext.state === 'suspended') {
            await this.audioContext.resume();
        }
    }

    private authQuery(): string {
        const date = new Date().toUTCString();
        const signatureOrigin = `host: ${this.host}\n` +
            `date: ${date}\n` +
            `GET ${this.path} HTTP/1.1`;
        const sha = CryptoJS.HmacSHA256(signatureOrigin, this.APISecret);
        const signature = CryptoJS.enc.Base64.stringify(sha);
        const authorizationOrigin = `api_key="${this.APIKey}", algorithm="hmac-sha256", headers="host date request-line", signature="${signature}"`;
        const authorization = btoa(authorizationOrigin);
        // 与官方一致：authorization 不做 urlencode
        return `authorization=${authorization}&date=${encodeURIComponent(date)}&host=${this.host}`;
    }

    private async open(url: string, text: string, params: TTSParams) {
        this.ws?.close();
        this.ws = new WebSocket(url);
        this.ws.binaryType = 'arraybuffer';

        const send = () => {
            const rate = params.sampleRate ?? 16000;
            const frame = {
                common: { app_id: this.APPID },
                business: {
                    aue: 'raw',
                    auf: `audio/L16;rate=${rate}`,
                    vcn: params.vcn ?? 'xiaoyan',
                    tte: 'UTF8',
                    speed: params.speed ?? 50,
                    volume: params.volume ?? 50,
                    pitch: params.pitch ?? 50
                },
                data: {
                    status: 2,
                    text: btoa(unescape(encodeURIComponent(text)))
                }
            };
            this.ws?.send(JSON.stringify(frame));
        };

        const chunks: Float32Array[] = [];

        this.ws.onopen = () => send();
        this.ws.onmessage = async (e) => {
            const res = JSON.parse(e.data);
            if (res.code !== 0) { this.stop(); return; }
            if (res.data?.audio) {
                chunks.push(this.b64ToF32(res.data.audio));
            }
            if (res.data?.status === 2) {
                await this.play(merge(chunks));
                this.stop();
            }
        };
        this.ws.onerror = () => { this.stop(); };

        const merge = (arr: Float32Array[]) => {
            const total = arr.reduce((s, a) => s + a.length, 0);
            const out = new Float32Array(total);
            let off = 0; arr.forEach(a => { out.set(a, off); off += a.length; });
            return out;
        };
    }

    private async play(samples: Float32Array) {
        if (!this.audioContext) return;
        const buf = this.audioContext.createBuffer(1, samples.length, 16000);
        buf.copyToChannel(samples, 0, 0);
        const src = this.audioContext.createBufferSource();
        src.buffer = buf;
        src.connect(this.audioContext.destination);
        return new Promise<void>(r => { src.onended = () => r(); src.start(0); });
    }

    private b64ToF32(b64: string): Float32Array {
        const bin = atob(b64);
        const bytes = new Uint8Array(bin.length);
        for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
        const dv = new DataView(bytes.buffer);
        const out = new Float32Array(bytes.length / 2);
        for (let i = 0; i < out.length; i++) out[i] = dv.getInt16(i * 2, true) / 32768;
        return out;
    }
}

export const ttsService = new XfTTSService();



